/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.internal.ui.preferences.formatter;

import java.util.Map;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.MarginPainter;
import org.eclipse.jface.text.WhitespaceCharacterPainter;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes;
import org.eclipse.php.internal.ui.PHPUiPluginExt;
import org.eclipse.php.internal.ui.preferences.PHPPreferenceConstants;
import org.eclipse.php.internal.ui.preferences.SimplePHPSourceViewerConfiguration;
import org.eclipse.php.internal.ui.text.PHPTextTools;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants;

/**
 * <p>
 * An abstract entity to create a preview window, similar to the <tt>JDT's</tt>
 * <code>JavaPreview</code>.
 * </p>
 *
 * @author Sean W. Quinn (swquinn@gmail.com)
 */
@SuppressWarnings("restriction")
public abstract class PHPPreview {

  /**
   * @author Sean W. Quinn (swquinn@gmail.com)
   */
  private final class PHPSourcePreviewerUpdater {

    final IPropertyChangeListener fontListener = new IPropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent event) {
        if (event.getProperty().equals(PHPPreferenceConstants.EDITOR_TEXT_FONT)) {
          final Font font = JFaceResources.getFont(PHPPreferenceConstants.EDITOR_TEXT_FONT);
          fSourceViewer.getTextWidget().setFont(font);
          if (fMarginPainter != null) {
            fMarginPainter.initialize();
          }
        }
      }
    };

    final IPropertyChangeListener propertyListener = new IPropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent event) {
        if (viewerConfiguration.affectsTextPresentation(event)) {
          viewerConfiguration.handlePropertyChangeEvent(event);
          fSourceViewer.invalidateTextPresentation();
        }
      }
    };

    public PHPSourcePreviewerUpdater() {

      JFaceResources.getFontRegistry().addListener(fontListener);
      fPreferenceStore.addPropertyChangeListener(propertyListener);

      fSourceViewer.getTextWidget().addDisposeListener(new DisposeListener() {
        public void widgetDisposed(DisposeEvent e) {
          JFaceResources.getFontRegistry().removeListener(fontListener);
          fPreferenceStore.removePropertyChangeListener(propertyListener);
        }
      });
    }
  }

  protected final SimplePHPSourceViewerConfiguration viewerConfiguration;
  protected final Document fPreviewDocument;
  protected final SourceViewer fSourceViewer;
  protected final IPreferenceStore fPreferenceStore;

  protected final MarginPainter fMarginPainter;

  /**
   * Map of "working" settings. These are the settings of the currently selected
   * profile that is either being viewed or edited.
   *
   * @see org.eclipse.php.internal.ui.preferences.formatter.profile.PHPProfile#getSettings()
   */
  protected Map<String, String> fWorkingValues;

  private int fTabSize = 0;
  private WhitespaceCharacterPainter fWhitespaceCharacterPainter;

  public PHPPreview(Map<String, String> workingValues, Composite parent) {
    PHPTextTools tools = PHPUiPluginExt.getDefault().getTextTools();
    fPreviewDocument = new Document();
    fWorkingValues = workingValues;
    tools.setupDocumentPartitioner(fPreviewDocument, PHPPartitionTypes.PHP_DEFAULT);

    PreferenceStore prioritizedSettings = new PreferenceStore();
    //prioritizedSettings.setValue(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5);
    //prioritizedSettings.setValue(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_5);
    //prioritizedSettings.setValue(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_5);
    //prioritizedSettings.setValue(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, JavaCore.ERROR);

    IPreferenceStore[] chain = { }; //prioritizedSettings, PHPUiPluginExt.getDefault().getCombinedPreferenceStore() };
    fPreferenceStore = new ChainedPreferenceStore(chain);
    fSourceViewer = new SourceViewer(parent, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
    fSourceViewer.setEditable(false);
    Cursor arrowCursor = fSourceViewer.getTextWidget().getDisplay().getSystemCursor(SWT.CURSOR_ARROW);
    fSourceViewer.getTextWidget().setCursor(arrowCursor);
    fSourceViewer.getTextWidget().setCaret(null);

    viewerConfiguration = new SimplePHPSourceViewerConfiguration(tools.getColorManager(), fPreferenceStore, null,
        PHPPartitionTypes.PHP_DEFAULT, true);
    fSourceViewer.configure(viewerConfiguration);
    fSourceViewer.getTextWidget().setFont(JFaceResources.getFont(PHPPreferenceConstants.EDITOR_TEXT_FONT));

    fMarginPainter = new MarginPainter(fSourceViewer);
    final RGB rgb = PreferenceConverter.getColor(fPreferenceStore,
        AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR);
    fMarginPainter.setMarginRulerColor(tools.getColorManager().getColor(rgb));
    fSourceViewer.addPainter(fMarginPainter);

    new PHPSourcePreviewerUpdater();
    fSourceViewer.setDocument(fPreviewDocument);
  }

  public Control getControl() {
    return fSourceViewer.getControl();
  }

  public void update() {
    if (fWorkingValues == null) {
      fPreviewDocument.set(""); //$NON-NLS-1$
      return;
    }

    // update the print margin
    final String value = (String) fWorkingValues.get(DefaultCodeFormatterConstants.FORMATTER_LINE_SPLIT);
    final int lineWidth = getPositiveIntValue(value, 0);
    fMarginPainter.setMarginRulerColumn(lineWidth);

    // update the tab size
    final int tabSize = getPositiveIntValue((String) fWorkingValues
        .get(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE), 0);
    if (tabSize != fTabSize)
      fSourceViewer.getTextWidget().setTabs(tabSize);
    fTabSize = tabSize;

    final StyledText widget = (StyledText) fSourceViewer.getControl();
    final int height = widget.getClientArea().height;
    final int top0 = widget.getTopPixel();

    final int totalPixels0 = getHeightOfAllLines(widget);
    final int topPixelRange0 = totalPixels0 > height ? totalPixels0 - height : 0;

    widget.setRedraw(false);
    doFormatPreview();
    fSourceViewer.setSelection(null);

    final int totalPixels1 = getHeightOfAllLines(widget);
    final int topPixelRange1 = totalPixels1 > height ? totalPixels1 - height : 0;

    final int top1 = topPixelRange0 > 0 ? (int) (topPixelRange1 * top0 / (double) topPixelRange0) : 0;
    widget.setTopPixel(top1);
    widget.setRedraw(true);
  }

  private int getHeightOfAllLines(StyledText styledText) {
    int height = 0;
    int lineCount = styledText.getLineCount();
    for (int i = 0; i < lineCount; i++)
      height = height + styledText.getLineHeight(styledText.getOffsetAtLine(i));
    return height;
  }

  protected abstract void doFormatPreview();

  private static int getPositiveIntValue(String string, int defaultValue) {
    try {
      int i = Integer.parseInt(string);
      if (i >= 0) {
        return i;
      }
    }
    catch (NumberFormatException e) {
    }
    return defaultValue;
  }

  /**
   * @return the working values.
   */
  public Map<String, String> getWorkingValues() {
    return fWorkingValues;
  }

  /**
   * @param workingValues sets the working values of this preview.
   */
  public void setWorkingValues(Map<String, String> workingValues) {
    fWorkingValues = workingValues;
  }

  /**
   * <p>
   * Sets whether or not to show invisible characters in the preview window. If
   * enabled, the user will see <tt>"&para;"</tt> (paragraph/link breaks),
   * <tt>"&middot;"</tt> (spaces), and <tt>"&raquo;"</tt> (tabs).
   * </p>
   *
   * @param enable <tt>true</tt> to enable and show invisible characters,
   *          <tt>false</tt> to disable and hide invisible characters.
   */
  public void showInvisibleCharacters(boolean enable) {
    if (enable) {
      if (fWhitespaceCharacterPainter == null) {
        fWhitespaceCharacterPainter = new WhitespaceCharacterPainter(fSourceViewer);
        fSourceViewer.addPainter(fWhitespaceCharacterPainter);
      }
    }
    else {
      fSourceViewer.removePainter(fWhitespaceCharacterPainter);
      fWhitespaceCharacterPainter = null;
    }
  }
}
